package ru.amse.stroganova.ui.action;

import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;

import ru.amse.stroganova.algorythms.MinimalSpanningTree;
import ru.amse.stroganova.algorythms.TopologicalSort;
import ru.amse.stroganova.ui.GraphComponent;
import ru.amse.stroganova.ui.command.Command;
import ru.amse.stroganova.ui.layout.Layout;
import ru.amse.stroganova.ui.layout.MSTLayout;
import ru.amse.stroganova.ui.layout.TopologicalLayout;

/**
 * Presents a factory for graph component algorythmic actions.
 * 
 * @author Dasha Stroganova
 * 
 */
public class AlgorythmActionsFactory {

	private final GraphComponent component;
	
	private final Action topSortAction;
	
	private final Action mstAction;
	
	private final Action dijkstraAction;
	
	private final Action maxFlowAction;
	
	private final List<AlgorythmMessageListener> listeners;
	
	private boolean isMessageActive = false;
	
	/**
	 * Creates new algorythm actions factory for given graph component.
	 * 
	 * @param component
	 */
	public AlgorythmActionsFactory(GraphComponent component) {
		this.component = component;
		topSortAction = new TopSortAction();
		mstAction = new MinSpanningTreeAction();
		dijkstraAction = new DijkstraAction();
		maxFlowAction = new MaxFlowAction();
		listeners = new ArrayList<AlgorythmMessageListener>();
	}
	
	/**
	 * Adds the specified AlgorythmErrorListener to recive notification about state
	 * change.
	 * 
	 * @param listener
	 */
	public void addAlgorythmErrorListener(AlgorythmMessageListener listener) {
		listeners.add(listener);
	}
	
	/**
	 * Removes the specified AlgorythmErrorListener from this presentation.
	 * 
	 * @param listener
	 */
	public void removeAlgorythmErrorListener(AlgorythmMessageListener listener) {
		listeners.remove(listener);
	}
	
	private void fireMessageRecieved(String message, boolean isErrorMessage) {
		isMessageActive = true;
		for (AlgorythmMessageListener listener : listeners) {
			listener.messageRecieved(message, isErrorMessage);
		}
	}
	
	private void fireMessageUnactive() {
		isMessageActive = false;
		for (AlgorythmMessageListener listener : listeners) {
			listener.messageUnactive();
		}
	}
	
	/**
	 * Makes required actions if message is no longer active.
	 *
	 */
	public void messageUnactive() {
		if (isMessageActive) {
			fireMessageUnactive();
		}
	}
	
	/**
	 * Makes required actions if message is active.
	 * @param message 
	 * @param isErrorMessage 
	 *
	 */
	public void meaasgeActive(String message, boolean isErrorMessage) {
		fireMessageRecieved(message, isErrorMessage);
	}
	
	/**
	 * Returns an action for topological sort.
	 * 
	 * @return action
	 */
	public Action getTopSortAction() {
		return topSortAction;
	}
	
	/**
	 * Returns an action for Dijkstra algorythm.
	 * 
	 * @return action
	 */
	public Action getDijkstraTreeAction() {
		return dijkstraAction;
	}
	
	/**
	 * Returns an action for minimal spanning tree algorythm.
	 * 
	 * @return action
	 */
	public Action getMinSpanningTreeAction() {
		return mstAction;
	}
	
	/**
	 * Returns an action for maximum flow algorythm.
	 * 
	 * @return action
	 */
	public Action getMaxFlowAction() {
		return maxFlowAction;
	}
	
	private class TopSortAction  extends AbstractAction {
		
		/* package */TopSortAction() {
			super("Topological Sort");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			component.getGraphElementSelection().unselect();
			component.getGraphElementMarker().unmark();
			TopologicalSort topSort = new TopologicalSort(component.getGraphPresentation().getGraph());
			if (topSort.hasLoops()) {
				component.getGraphElementMarker().markEdges(topSort.getLoop(), true);
				fireMessageRecieved(topSort.getErrorMessage(), true);
			} else {
				Layout topLayout = new TopologicalLayout(component.getGraphPresentation(), topSort.getSortedVertices());
				Command command = component.getCommandFactory().getNewLayoutCommand(topLayout);
				component.addCommand(command);
				command.execute();
			}
		}
	}
	
	private class MinSpanningTreeAction  extends AbstractAction {
		
		/* package */MinSpanningTreeAction() {
			super("Minimal Spanning Tree");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			component.getGraphElementSelection().unselect();
			component.getGraphElementMarker().unmark();
			MinimalSpanningTree mst = new MinimalSpanningTree(component.getGraphPresentation().getGraph());
			if (!mst.isConnected()) {
				fireMessageRecieved(mst.getErrorMessage(), true);
			} else {
				component.getGraphElementMarker().markEdges(mst.getTreeEdges(), false);
				Layout mstLayout = new MSTLayout(component.getGraphPresentation(), mst.getTreeEdges(), component.getWidth());
				Command command = component.getCommandFactory().getNewLayoutCommand(mstLayout);
				component.addCommand(command);
				command.execute();
			}		
		}
	}

	private class DijkstraAction extends AbstractAction {
		
		/* package */DijkstraAction() {
			super("Dijkstra Algorythm");
			setEnabled(false);
		}
		
		public void actionPerformed(ActionEvent e) {
			component.getGraphElementSelection().unselect();
			component.getGraphElementMarker().unmark();
			fireMessageRecieved("Select start vertex..", false);
			component.getEditingActionsFactory().getSelectAction().putValue(AbstractAction.SMALL_ICON, new ImageIcon(ClassLoader.getSystemResource("img/select_alg.gif")));
			component.setTool(component.getToolFactory().getDijkstraSelectionTool());
		}
	}
	
	private class MaxFlowAction extends AbstractAction {
		
		/* package */MaxFlowAction() {
			super("Maximum Flow");
			setEnabled(false);
		}
		
		public void actionPerformed(ActionEvent e) {
			component.getGraphElementSelection().unselect();
			component.getGraphElementMarker().unmark();
			component.getEditingActionsFactory().getSelectAction().putValue(AbstractAction.SMALL_ICON, new ImageIcon(ClassLoader.getSystemResource("img/select_alg.gif")));
			component.setTool(component.getToolFactory().getMaxFlowSelectionTool());
		}
	}

}
